/*
 * Copyright (c) 2021, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <signal.h>
#include <fcntl.h>
#include <stdarg.h>

#include "virtnet.h"
#include "virtnet_rpc.h"
#include "virtnet_controller.h"

#define VIRTNET_MAX_LINE_LEN 255
#define VIRTNET_MAX_VAL_STR_LEN 21 /* strlen(str(-2^63)) + '\0' .*/

static char *signals [] = {
	[SIGINT] = "SIGINT",
	[SIGILL] = "SIGILL",
	[SIGBUS] = "SIGBUS",
	[SIGFPE] = "SIGFPE",
	[SIGSEGV] = "SIGSEGV",
	[SIGPIPE] = "SIGPIPE",
	[SIGTERM] = "SIGTERM",
	[_NSIG] = "MAXSIGNUM",
};

/* forward declariation. */
static inline size_t virtnet_strnlen(const char *s, size_t max_len);
static void virtnet_signal_handler(int signum, siginfo_t *info,
				   void *ucontext);
static inline void virtnet_reverse(char *str, uint64_t len);
static char *virtnet_utoa(uint64_t val, int base, char *buf);
static char *virtnet_itoa(int64_t val, int base, char *buf);
static void virtnet_check_longlong(const char **fmt, uint8_t *longlong);
static void virtnet_fmt_flag_parse
		(char **str, char *end, const char **fmt, va_list ap);
static int virtnet_vsnprintf
		(char *str, size_t size, const char *fmt, va_list ap);
static int virtnet_printf(const char *fmt, ...);
static void virtnet_sfs_remove(void);
static void virtnet_signal_info(siginfo_t *info);
#ifdef __x86_64__
static void virtnet_reg_x86_64_info(void *ucontext);
#endif
#ifdef __arm__
static void virtnet_reg_arm_info(void *ucontext);
#endif
static void virtnet_reg_info(void *ucontext);

static void *ss_sp = NULL;
extern bool quit;

/* APIs */
void virtnet_signal_handler_install(void)
{
	int ret, i;
	stack_t ss;
	struct sigaction sa;

	ss_sp = calloc(1, SIGSTKSZ);
	err_if(!ss_sp, "cannot calloc signal handler stack, quit...");
	ss.ss_sp = ss_sp;
	ss.ss_size = SIGSTKSZ;
	ss.ss_flags = 0;
	ret = sigaltstack(&ss, NULL);
	err_if(ret == -1, "cannot set sigalstack, quit...");
	sa.sa_flags = SA_ONSTACK | SA_SIGINFO;
	sa.sa_sigaction = virtnet_signal_handler;
	sigemptyset(&sa.sa_mask);
	for (i = 0; i < _NSIG; i++) {
		if (signals[i] == NULL)
			continue;
		ret = sigaction(i, &sa, NULL);
		err_if(ret == -1,
		       "cannot install sighandler for %s, quit...",
		       signals[i]);
	}
	return;
free_exit:
	exit(EXIT_FAILURE);
}

void virtnet_signal_handler_uninstall(void)
{
	int i;
	struct sigaction sa;

	if (ss_sp)
		free(ss_sp);
	ss_sp = NULL;
	sa.sa_handler = SIG_DFL;
	sigemptyset(&sa.sa_mask);
	for (i = 0; i < _NSIG; i++) {
		if (signals[i] == NULL)
			continue;
		sigaction(i, &sa, NULL);
	}
}

/*
 * Callback for system signal.
 *
 * All functions called should be async-signal-safty.
 */
static void
virtnet_signal_handler(int signum, siginfo_t *info, void *ucontext)
{
	(void)info;
	(void)ucontext;
	virtnet_printf("\nSignal %s received", signals[signum]);
	switch (signum) {
	case SIGPIPE:
		/* Ignore */
		break;
	case SIGINT:
		/* FALLTHROUGH. */
	case SIGTERM:
		virtnet_printf(", preparing to exit...\n"
			       "Kill virtio_net_controller\n");
		quit = true;
		break;
	case SIGILL:
		/* FALLTHROUGH. */
	case SIGBUS:
		/* FALLTHROUGH. */
	case SIGFPE:
		/* FALLTHROUGH. */
	case SIGSEGV:
		virtnet_signal_info(info);
		virtnet_reg_info(ucontext);
		virtnet_sfs_remove();
		abort();
	default:
		abort(); /* We shouldnot be here, coredump... */
	}
}

/**
 * An async-safety version of glibc strnlen.
 *
 * This version is not efficent but is async safe and
 * intened to be used in signal handler, or other circumstance
 * which needs async-safety functions.
 */
static inline size_t
virtnet_strnlen(const char *s, size_t max_len)
{
	size_t i;

	if (s == NULL)
		return 0;
	if (max_len == 0)
		return 0;
	for (i = 0; s[i] != '\0' && i < max_len; i++)
		; /* Nothing. */
	return i;
}

static inline void
virtnet_reverse(char *str, uint64_t len)
{
	uint64_t start = 0;
	uint64_t end = len - 1;
	char tmp;

	while (start < end) {
		tmp = *(str + start);
		*(str + start) = *(str + end);
		*(str + end) = tmp;
		start++;
		end--;
	}
}

/**
 * An async-safety version of glibc utoa.
 *
 * This version is not efficent but is async safe and
 * intened to be used in signal handler, or other circumstance
 * which needs async-safety functions.
 */
static char *
virtnet_utoa(uint64_t val, int base, char *buf)
{
	const char hex[] = "0123456789abcdef";
	uint64_t len = 0;

	if (val == 0) {
		buf[len++] = '0';
		buf[len] = '\0';
		return buf;
	}
	while (val != 0) {
		buf[len++] = hex[val % base];
		val /= base;
	}
	buf[len] = '\0';
	virtnet_reverse(buf, len);
	return buf;
}

/**
 * An async-safety version of glibc itoa.
 *
 * This version is not efficent but is async safe and
 * intened to be used in signal handler, or other circumstance
 * which needs async-safety functions.
 */
static char *
virtnet_itoa(int64_t val, int base, char *buf)
{
	uint8_t negative = !!(val < 0);
	uint64_t tmp = 0;

	if (negative && base == 10) {
		tmp = (uint64_t)(-val);
		virtnet_utoa(tmp, base, buf + 1);
		*buf = '-';
		return buf;
	}
	tmp = (uint64_t)val;
	virtnet_utoa(tmp, base, buf);
	return buf;
}

static void
virtnet_check_longlong(const char **fmt, uint8_t *longlong)
{
	*longlong = 0;
	if (**fmt == 'l') {
		(*fmt)++;
		if (**fmt == 'l') {
			(*fmt)++;
			*longlong = 1;
		} else
			*longlong = !!(sizeof(long) == sizeof(int64_t));
	}
}

static void
virtnet_fmt_flag_parse(char **str, char *end, const char **fmt, va_list ap)
{
	uint8_t longlong = 0;
	char val_str[VIRTNET_MAX_VAL_STR_LEN];
	int base = 10;
	char *val_str_ptr = NULL;
	char *sval = NULL;
	uint64_t uval = 0;
	int64_t ival = 0;

	virtnet_check_longlong(fmt, &longlong);
	switch (**fmt) {
	case 'x':
	case 'p':
	case 'i':
	case 'd':
	case 'u':
		if (**fmt == 'p') {
			longlong = !!(sizeof(void *) == sizeof(uint64_t));
			base = 16;
		}
		if (**fmt == 'x')
			base = 16;
		if (longlong) {
			if (**fmt == 'u')
				uval = va_arg(ap, uint64_t);
			else
				ival = va_arg(ap, int64_t);
		} else {
			if (**fmt == 'u')
				uval = va_arg(ap, uint32_t);
			else
				ival = va_arg(ap, int32_t);
		}
		val_str_ptr = (**fmt == 'u') ?
			virtnet_utoa(uval, base, val_str) :
			virtnet_itoa(ival, base, val_str);
		/* Since utoa is uint64_t, if we only have 'x' without 'll'. */
		if (**fmt == 'x' && !longlong && ival < 0)
			val_str_ptr += 8;
		while (*val_str_ptr && (*str) < end)
			*(*str)++ = *val_str_ptr++;
		return;
	case 's':
		sval = va_arg(ap, char *);
		if (!sval)
			sval = "(null)";
		while (*sval && (*str) < end)
			*(*str)++ = *sval++;
		return;
	}
}

/**
 * An async-safety version of glibc vsnprintf.
 *
 * This version is not efficent but is async safe and
 * intened to be used in signal handler, or other circumstance
 * which needs async-safety functions.
 *
 * This version is also very limited. Only some format flags are
 * supported.
 */
static int
virtnet_vsnprintf(char *str, size_t size, const char *fmt, va_list ap)
{
	char *start = NULL;
	char *end = NULL;

	if (size > VIRTNET_MAX_LINE_LEN)
		size = VIRTNET_MAX_LINE_LEN;
	start = str;
	end = start + size - 1;
	for (; *fmt; fmt++) {
		if (*fmt != '%') {
			if (str == end)
				break;
			*str++ = *fmt;
			continue;
		}
		fmt++; /* skip '%' .*/
		virtnet_fmt_flag_parse(&str, end, &fmt, ap);
	}
	*str = '\0';
	return (int)(str - start);
}

/**
 * An async-safety version of glibc printf.
 *
 * This version is not efficent but is async safe and
 * intened to be used in signal handler, or other circumstance
 * which needs async-safety functions.
 */
static int
virtnet_printf(const char *fmt, ... )
{
	int ret = 0;
	va_list args;
	char output[VIRTNET_MAX_LINE_LEN];

	va_start(args, fmt);
	virtnet_vsnprintf(output, VIRTNET_MAX_LINE_LEN, fmt, args);
	ret = write(STDOUT_FILENO, output,
	      virtnet_strnlen(output, VIRTNET_MAX_LINE_LEN));
	va_end(args);
	return (int)ret;
}

static void virtnet_sfs_remove(void)
{
	struct virtnet_device *dev;
	struct snap_context *sctx;
	int i;

	if (!ctx)
		return;

	sctx = ctx->sctx;

	virtnet_printf("Remove SFs...\n");
	for (i = 0; i < sctx->virtio_net_pfs.max_pfs; i++) {
		dev = &ctx->dev_list[i];
		if (dev)
			virtnet_sf_dev_destroy(dev);
	}
}

static void
virtnet_signal_info(siginfo_t *info)
{
	virtnet_printf("\nsignal info:\n"
		       "\tsi_signo:\t%d\n"
		       "\tsi_code:\t%d\n"
		       "\tsi_errno:\t%d\n"
		       "\tsi_status:\t%d\n"
		       "\tsi_addr:\t0x%p\n",
		       info->si_signo, info->si_code, info->si_errno,
		       info->si_status, info->si_addr);
}

static void
virtnet_reg_info(void *ucontext)
{
#ifdef __x86_64__
	virtnet_reg_x86_64_info(ucontext);
#elif __arm__
	virtnet_reg_arm_info(ucontext);
#endif
}

#ifdef __x86_64__
static void
virtnet_reg_x86_64_info(void *ucontext)
{
	ucontext_t *uc = (ucontext_t *)ucontext;

	virtnet_printf("x86_64 regs:\n"
		       "\tREG_RIP:\t0x%x\n"
		       "\tREG_RSP:\t0x%x\n",
		       uc->uc_mcontext.gregs[REG_RIP],
		       uc->uc_mcontext.gregs[REG_RSP]);
}
#endif

#ifdef __arm__
static void
virtnet_reg_arm_info(void *ucontext)
{
	ucontext_t *uc = (ucontext_t *)ucontext;

	virtnet_printf("ARM regs:\n"
		       "\tPC:\t0x%x\n"
		       "\tSP:\t0x%x\n"
		       "\tIP:\t0x%x\n",
		       uc->uc_mcontext.arm_pc, uc->uc_mcontext.arm_sp,
		       uc->uc_mcontext.arm_ip);
}
#endif
